﻿using System;
using System.Collections.Generic;
using System.Xml;
using NeuralNetwork.Neurons;
using NeuralNetwork.Generic;

namespace NeuralNetwork.Layers
{
    
    public class Layer
    {
        private string id;

        // Reference to paretn network
        public Network network = null;

        // Neighbour Layer 
        public Layer forward = null;

        // Neurons
        protected static Factory<Neuron> neuronFactory = new Factory<Neuron>();
        public List<Neuron> neurons = new List<Neuron>();

        protected Layer()
        { 
        
        }

        public Layer(Network network)
        {
            this.network = network;
            this.id = network.GetUniqueID();            
        }

        public Layer(Network network, int neurons, string neurontype):this(network)
        {            
            for (int i = 0; i < neurons; i++) AddNeuron(neurontype);
        }

        public string ID
        {
            get { return id; }
            set { id = value; }
        }

        public void AddNeuron(string neurontype)
        {
            Neuron n = neuronFactory.Make(neurontype, network);
            if (n != null) neurons.Add(n);
        }

        public bool Connect(Layer layer)
        {
            if (network == null) return false;

            forward = layer;

            Axon a = null;
            foreach (Neuron i in neurons)
            { 
                foreach(Neuron j in layer.neurons)
                {
                    a = new Axon(i, j, 2 * network.rand.NextDouble() - 1);
                    i.axons.Add(a);
                    j.dendrites.Add(a);
                }
            }

            return true;
        }

        virtual public void Prune()
        {
            Neuron n = null;
            for (int i = neurons.Count - 1; i >= 0; i--)
            {
                n = neurons[i];
                if (n.axons.Count == 0)
                    neurons.RemoveAt(i);
                else
                    n.Prune();
            }
        }

        virtual public void Update()
        {
            foreach (Neuron n in neurons) n.Update();
            if (forward != null) forward.Update();
        }

        virtual public void Revert()
        {
            foreach (Neuron n in neurons) n.Revert();
        }
    
        virtual public void Reset()
        {
            foreach (Neuron n in neurons) n.Reset();
        }

        virtual public XmlNode Save(XmlDocument doc, XmlNode parent)
        {
            XmlNode node = network.AddChildToNode(doc, parent, this.GetType().Name, null);
            network.AddIDToNode(doc, node, id);
            if (forward != null) network.AddChildToNode(doc, node, "forward", forward.ID);

            XmlNode neuronNodes = network.AddChildToNode(doc, node, "neurons", null);
            foreach (Neuron n in neurons) n.Save(doc, neuronNodes);

            return node;
        }

        virtual public void Load(XmlNode layerNode)
        {
            // Get Layer data 
            foreach (XmlNode n in layerNode.ChildNodes)
            {
                switch (n.Name.ToUpper())
                {
                    case "FORWARD":
                        string fid = n.InnerText.Trim();
                        foreach (Layer l in network.layers) if (l.ID == fid) forward = l;
                        break;

                    default:
                        //ignore
                        break;
                }
            }

            // Read Neurons of the layer
            foreach (XmlNode neuronNodes in layerNode)
            {
                switch (neuronNodes.Name.ToUpper())
                {
                    case "NEURONS":
                        foreach (XmlNode neuronNode in neuronNodes.ChildNodes)
                        {
                            Neuron neuron = neuronFactory.Make(neuronNode.Name, network);                            
                            if (neuron != null)
                            {
                                foreach (XmlAttribute attr in neuronNode.Attributes)
                                    if (attr.Name.ToUpper() == "ID") neuron.ID = attr.Value.Trim();
                                neuron.Load(neuronNode);
                                neurons.Add(neuron);                                
                            }
                        }
                        break;

                    default:
                        //ignore
                        break;
                }
            }
        }
    }
}
